package net.pws.common.persistence.hibernate3;

import java.io.Serializable;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.pws.common.persistence.EntityManager;
import net.pws.common.persistence.IPage;
import net.pws.common.persistence.UnitOfWork;

import org.hibernate.NonUniqueResultException;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.Assert;


/**
 */
@Transactional
public class HibernateEntityManager extends DaoSupportExtension implements
                                                               EntityManager {
    
    private static Map hqlOfGetAll = new HashMap();
        
    private RowMapper<Map> mapRowMapper;
    
    @Transactional(readOnly = true)
    public <T> T getReference(Class<T> entityClazz, Serializable id) {
        if (id == null) {
            logger.warn("Load entity of " + entityClazz
                        + " but specified id is null!");
            return null;
        }
        return (T) getHibernateTemplate().load(entityClazz, id);
    }
    
    @Transactional(readOnly = true)
    public <T> T find(Class<T> entityClazz, Serializable id) {
        return (T) getHibernateTemplate().get(entityClazz, id);
    }
    
    @Transactional(readOnly = true)
    public void refresh(Object entity) {
        getHibernateTemplate().refresh(entity);
    }
    
    @Transactional(readOnly = true)
    public List findAll(Class entityClazz) {
        String hql = (String) hqlOfGetAll.get(entityClazz);
        return getHibernateTemplate().find(hql);
    }
    
    @Transactional(readOnly = true)
    public List findList(String queryStatement, Map parameters) {
        return getHibernateTemplate().findByNamedParam(queryStatement,
                                                       parameters);
    }
    
    @Transactional(readOnly = true)
    public IPage findPage(String queryStatement4Count,
                          String queryStatement,
                          Map parameters,
                          int start,
                          int maxResultsInPage) {
        return getHibernateTemplate().queryByPage(queryStatement4Count,
                                                  queryStatement,
                                                  parameters,
                                                  start,
                                                  maxResultsInPage);
    }
    
    @Transactional(readOnly = true)
    public IPage findPage(String queryStatement4Count,
                          String queryStatement,
                          Object[] parameters,
                          int start,
                          int maxResultsInPage) {
        return getHibernateTemplate().queryByPage(queryStatement4Count,
                                                  queryStatement,
                                                  parameters,
                                                  start,
                                                  maxResultsInPage);
    }
    
    @Transactional(readOnly = true)
    public IPage findPage(String queryStatement,
                          Map parameters,
                          int start,
                          int maxResultsInPage) {
        return getHibernateTemplate().queryByPage(queryStatement,
                                                  parameters,
                                                  start,
                                                  maxResultsInPage);
    }
    
    @Transactional(readOnly = true)
    public IPage findPage(String queryStatement,
                          Object[] parameters,
                          int start,
                          int maxResultsInPage) {
        return getHibernateTemplate().queryByPage(queryStatement,
                                                  parameters,
                                                  start,
                                                  maxResultsInPage);
    }
    
    @Transactional(readOnly = true)
    public Object findUnique(String queryStatement, Map parameters) {
        List result = getHibernateTemplate().findByNamedParam(queryStatement,
                                                              parameters);
        if (result.isEmpty()) {
            return null;
        }
        return result.get(0);
    }
    
    @Transactional(readOnly = true)
    public IPage queryForPage(Class entityClazz,
                              final int start,
                              final int maxResultsInPage) {
        Assert.isTrue(start >= 0, "start should start from 0");
        return getHibernateTemplate().queryByPagination(entityClazz,
                                                        start,
                                                        maxResultsInPage);
    }
    
    public Serializable save(Object o) {
        return getHibernateTemplate().save(o);
    }
    
    public void save(Collection os) {
        getHibernateTemplate().saveOrUpdateAll(os);
    }

    
    public Serializable[] save(final Collection objects, final int batchSize) {
        final List ids = (List) getHibernateTemplate().batchExecute(SessionCallback.SAVE,
                                                                    objects,
                                                                    null,
                                                                    batchSize,
                                                                    true);
        return (Serializable[]) ids.toArray(new Serializable[] {});
    }
    
    public Serializable[] save(final Object[] objects, final int batchSize) {
        final List ids = (List) getHibernateTemplate().batchExecute(SessionCallback.SAVE,
                                                                    objects,
                                                                    null,
                                                                    batchSize,
                                                                    true);
        return (Serializable[]) ids.toArray(new Serializable[] {});
    }
    
    public void update(Object o) {
        getHibernateTemplate().update(o);
    }
    
    public void update(final Collection objects, final int batchSize) {
        getHibernateTemplate().batchExecute(SessionCallback.UPDATE,
                                            objects,
                                            null,
                                            batchSize,
                                            false);
    }
    
    public void update(final Object[] objects, final int batchSize) {
        getHibernateTemplate().batchExecute(SessionCallback.UPDATE,
                                            objects,
                                            null,
                                            batchSize,
                                            false);
    }
    
    public void merge(Object o) {
        getHibernateTemplate().merge(o);
    }

    
    public void persist(Object o) {
        getHibernateTemplate().saveOrUpdate(o);
    }
    
    public void remove(Object o) {
        getHibernateTemplate().delete(o);
    }
    
    public void delete(final Object[] objects, int batchSize) {
        getHibernateTemplate().batchExecute(SessionCallback.DELETE,
                                            objects,
                                            null,
                                            batchSize,
                                            false);
    }
    
    public void delete(Collection objects, int batchSize) {
        getHibernateTemplate().batchExecute(SessionCallback.DELETE,
                                            objects,
                                            null,
                                            batchSize,
                                            false);
    }
    
    public void removeById(Class entityClazz, Serializable id) {
        remove(getReference(entityClazz, id));
    }
    
    public void deleteById(Class entityClazz, Collection ids, int batchSize) {
        getHibernateTemplate().batchExecute(SessionCallback.DELETE_BY_ID,
                                            ids,
                                            entityClazz,
                                            batchSize,
                                            false);
    }
    
    public void deleteById(Class entityClazz, Serializable[] ids, int batchSize) {
        getHibernateTemplate().batchExecute(SessionCallback.DELETE_BY_ID,
                                            ids,
                                            entityClazz,
                                            batchSize,
                                            false);
    }
    
    public void execute(UnitOfWork unitOfWork) {
        getHibernateTemplate().execute(unitOfWork);
    }
    
    public int execute(final String hql, final Map parameters) {
        return (Integer) getHibernateTemplate().executeUpdate(hql, parameters);
    }
    
    public int execute(String hql, Object[] parameters) {
        return (Integer) getHibernateTemplate().executeUpdate(hql, parameters);
    }
    
    public void flush() {
        getHibernateTemplate().flush();
    }
    
    public List findList(String hql, Object[] parameters) {
        return getHibernateTemplate().find(hql, parameters);
    }
    
    public Object findUnique(String hql, Object[] parameters) {
        List result = getHibernateTemplate().find(hql, parameters);
        if (result.isEmpty()) {
            return null;
        }
        if (result.size() > 1) {
            throw new NonUniqueResultException(result.size());
        }
        return result.get(0);
    }
    
    public Object findFirst(String hql, Object[] parameters) {
        List result = getHibernateTemplate().find(hql, parameters);
        if (result.isEmpty()) {
            return null;
        }
        return result.get(0);
    }
    
    public Object findFirst(String hql, Map parameters) {
        return getHibernateTemplate().find(hql, parameters);
    }
    
    public List nativeFindList(String sql,
                               Object[] parameters,
                               RowMapper rowMapper) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        
        return getJdbcTemplate().query(sql, parameters, rowMapper);
    }
    
    public List nativeFindList(String sql, Object[] parameters) {
        return nativeFindList(sql, parameters, mapRowMapper);
    }
    
    public IPage nativeFindPage(String sql,
                                Object[] parameters,
                                int start,
                                int limit) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        
        return nativeFindPage(sql,
                              parameters,
                              start,
                              limit,
                              mapRowMapper);
    }
    
    public IPage nativeFindPage(String sql,
                                Object[] parameters,
                                int start,
                                int limit,
                                RowMapper rowMapper) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        
        return getJdbcTemplate().queryByPage(sql,
                                             parameters,
                                             start,
                                             limit,
                                             rowMapper);
    }
    
    public IPage nativeFindPage(String sql4count,
                                String sql4list,
                                Object[] parameters,
                                int start,
                                int limit) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql4count:" + sql4count);
            logger.debug("sql4list:" + sql4list);
            logger.debug("parameters:" + parameters);
        }
        
        return nativeFindPage(sql4count,
                              sql4list,
                              parameters,
                              start,
                              limit,
                              mapRowMapper);
    }
    
    public IPage nativeFindPage(String sql4count,
                                String sql4list,
                                Object[] parameters,
                                int start,
                                int limit,
                                RowMapper rowMapper) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql4count:" + sql4count);
            logger.debug("sql4list:" + sql4list);
            logger.debug("parameters:" + parameters);
        }
        return getJdbcTemplate().queryByPage(sql4count,
                                             sql4list,
                                             parameters,
                                             start,
                                             limit,
                                             rowMapper);
    }
    
    public Object nativeFindUnique(String sql,
                                   Object[] parameters,
                                   RowMapper rowMapper) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        List result = getJdbcTemplate().query(sql, parameters, rowMapper);
        if (result.isEmpty()) {
            return null;
        }
        if (result.size() > 1) {
            throw new NonUniqueResultException(result.size());
        }
        return result.get(0);
    }
    
    public Object nativeFindUnique(String sql, Object[] parameters) {
        return nativeFindUnique(sql, parameters, mapRowMapper);
    }
    
    public Object nativeFindFirst(String sql,
                                  Object[] parameters,
                                  RowMapper rowMapper) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        List result = getJdbcTemplate().query(sql, parameters, rowMapper);
        if (result.isEmpty()) {
            return null;
        }
        return result.get(0);
    }
    
    public Object nativeFindFirst(String sql, Object[] parameters) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        return nativeFindFirst(sql, parameters, mapRowMapper);
    }
    
    public int nativeExecute(String sql, Object[] parameters) {
        if (logger.isDebugEnabled()) {
            logger.debug("sql:" + sql);
            logger.debug("parameters:" + parameters);
        }
        return getJdbcTemplate().update(sql, parameters);
    }
    
    public RowMapper<Map> getMapRowMapper() {
        return mapRowMapper;
    }

    public void setMapRowMapper(RowMapper<Map> mapRowMapper) {
        this.mapRowMapper = mapRowMapper;
    }
    
}
